use crate::{
    data::{ArithmeticCommand, Command, CommandType},
    translator::counter::FunctionCallCounter,
};
use anyhow::{Result, anyhow};
use asm::AsmGenerator;
use function::{Call, Function, Return};
use goto::Goto;

pub(crate) mod asm;
mod function;
mod goto;

#[derive(Debug)]
pub(crate) struct AsmWriter<'a> {
    /// The commands to be translated.
    commands: Option<Vec<Command>>,
    /// The stack of labels.
    /// reserved for call command
    #[allow(dead_code)]
    call_return_labels: Vec<String>,
    /// The name of the VM file.
    vm_file_name: String,
    /// asm code cache
    cache: Vec<String>,
    /// current logical operation label counter
    jmp_label_counter: u32,
    /// call return label counter
    call_return_label_counter: &'a mut FunctionCallCounter,
    /// the current function label
    current_function_label: Option<String>,
}

impl<'a> AsmWriter<'a> {
    pub(crate) fn new(
        commands: Vec<Command>,
        vm_file_name: &str,
        call_return_label_counter: &'a mut FunctionCallCounter,
    ) -> Self {
        Self {
            commands: Some(commands),
            call_return_labels: Vec::new(),
            cache: Vec::new(),
            vm_file_name: vm_file_name.to_string(),
            jmp_label_counter: 0,
            current_function_label: None,
            call_return_label_counter,
        }
    }
    fn new_asm_generator(&self) -> AsmGenerator {
        AsmGenerator::new(&self.vm_file_name)
    }
    /// generate a new jump label
    fn new_jump_label(&mut self) -> String {
        self.jmp_label_counter += 1;
        format!("{}_JMP_{}", self.vm_file_name, self.jmp_label_counter)
    }

    /// translated the commands to asm instructions.
    pub(crate) fn tranlate_to_asm(mut self) -> Result<String> {
        for command in self.commands.take().unwrap() {
            self.cache.push(format!("\n//{}", command.vm_code));
            match command.command_type {
                CommandType::Push => {
                    let mut asm_generator = self.new_asm_generator();
                    asm_generator.push(
                        &command.arg1,
                        command
                            .arg2
                            .ok_or_else(|| anyhow!("push command mush has arg2"))?,
                    )?;
                    self.cache.push(asm_generator.output());
                }
                CommandType::Pop => {
                    let mut asm_generator = self.new_asm_generator();
                    asm_generator.pop(
                        &command.arg1,
                        command
                            .arg2
                            .ok_or_else(|| anyhow!("pop command mush has arg2"))?,
                    )?;
                    self.cache.push(asm_generator.output());
                }
                CommandType::Arithmetic => self.arithmetic_operator(&command.arg1)?,
                CommandType::Function => self.function_definition(&command)?,
                CommandType::Label => self.label_definition(&command)?,
                CommandType::Goto | CommandType::IfGoto => self.goto_definition(&command)?,
                CommandType::Call => self.call_definition(&command)?,
                CommandType::Return => self.return_definition(&command)?,
            }
        }
        Ok(self.cache.join("\n"))
    }

    /// arithmetic operator
    ///
    /// # arguments
    /// * `operator` - the operator to use
    fn arithmetic_operator(&mut self, operator: &str) -> Result<()> {
        let operator = ArithmeticCommand::try_from(operator)?;
        let mut asm_generator = self.new_asm_generator();
        if ![ArithmeticCommand::Not, ArithmeticCommand::Neg].contains(&operator) {
            asm_generator.pop_to_dreg();
        }
        asm_generator.papare_pop_from_stack();
        let jmp_label = operator.is_comparable().then(|| self.new_jump_label());
        asm_generator.add_other(operator.to_asm(jmp_label.as_deref()));
        if !operator.is_comparable() {
            asm_generator.write_dreg_to_stack();
        }
        asm_generator.sp_inc();
        self.cache.push(asm_generator.output());
        Ok(())
    }

    /// function definition
    ///
    /// # arguments
    /// * `command` - the command to parse
    fn function_definition(&mut self, command: &Command) -> Result<()> {
        let function = Function::try_from(command)?;
        self.current_function_label = Some(function.label().into());
        self.cache.push(function.to_asm(&self.vm_file_name));
        Ok(())
    }

    /// call definition
    ///
    /// # arguments
    /// * `command` - the command to parse
    fn call_definition(&mut self, command: &Command) -> Result<()> {
        let mut call = Call::try_from(command)?;
        let counter = self.call_return_label_counter.call(&call.name);
        call.set_counter(counter);
        // println!("call counter: {counter},command: {command:#?}",);
        self.cache.push(call.to_asm(&self.vm_file_name));
        Ok(())
    }
    /// call definition
    ///
    /// # arguments
    /// * `command` - the command to parse
    fn return_definition(&mut self, command: &Command) -> Result<()> {
        let return_instruction = Return::try_from(command)?;
        self.cache.push(return_instruction.to_asm());
        Ok(())
    }

    /// function definition
    ///
    /// # arguments
    /// * `command` - the command to parse
    fn label_definition(&mut self, command: &Command) -> Result<()> {
        // format : (fileName.currentFunctionName.labelName)
        self.cache.push(format!(
            "({}${})",
            self.get_current_function_full_label(),
            command.arg1
        ));
        Ok(())
    }
    /// get current function label
    ///
    /// if the current function label is none, return the vm file name
    /// else return the {vm file name}.{current function label}
    fn get_current_function_full_label(&self) -> String {
        if self.current_function_label.is_none() {
            self.vm_file_name.clone()
        } else {
            self.current_function_label.as_deref().unwrap().into()
        }
    }

    /// function definition
    ///
    /// # arguments
    /// * `command` - the command to parse
    fn goto_definition(&mut self, command: &Command) -> Result<()> {
        let goto = Goto::try_from(command)?
            .set_current_function_label(self.get_current_function_full_label());
        self.cache.push(goto.to_asm());
        Ok(())
    }
}
